package ua.edu.nuos.jeetraining2012.cms.service;

import org.slf4j.Logger;
import ua.edu.nuos.jeetraining2012.cms.dto.GoogleCalendarDTO;
import ua.edu.nuos.jeetraining2012.cms.dto.predefined.DummyInstructorDTO;
import ua.edu.nuos.jeetraining2012.cms.dto.predefined.StandardUserStatuses;
import ua.edu.nuos.jeetraining2012.cms.entity.*;
import ua.edu.nuos.jeetraining2012.cms.model.CourseDAO;
import ua.edu.nuos.jeetraining2012.cms.model.StudentDAO;
import ua.edu.nuos.jeetraining2012.cms.model.StudentsToCoursesDAO;
import ua.edu.nuos.jeetraining2012.cms.model.UserDAO;
import ua.edu.nuos.jeetraining2012.cms.model.exception.DAOException;
import ua.edu.nuos.jeetraining2012.cms.service.exception.*;
import ua.edu.nuos.jeetraining2012.cms.util.google.api.service.exception.auth.GoogleAuthException;
import ua.edu.nuos.jeetraining2012.cms.util.google.api.service.exception.calendar.GoogleCalendarCreatingException;
import ua.edu.nuos.jeetraining2012.cms.util.google.api.service.exception.transport.GoogleTransportException;
import ua.edu.nuos.jeetraining2012.cms.util.google.api.services.calendar.GoogleCalendar;
import ua.edu.nuos.jeetraining2012.cms.util.mail.Mail;
import ua.edu.nuos.jeetraining2012.cms.util.mail.course.*;

import javax.annotation.Resource;
import javax.ejb.EJB;
import javax.ejb.Local;
import javax.ejb.Stateless;
import javax.validation.ConstraintViolation;
import javax.validation.ValidatorFactory;
import java.util.*;

/**
 * Course.
 * <p/>
 * Use this class to describe business logic in courses.
 *
 * @author Victor N. Ganichev victor.ganichev@gmail.com
 * @version 1.0 Date: 01.06.13 Time: 23:30
 * @since 1.0
 */
@Stateless
@Local
public class Course {

    /**
     * Define DAO layer.
     */
    @SuppressWarnings({"EjbEnvironmentInspection"})
    @EJB
    private CourseDAO dao;

    /** Define Student to courses DAO layer. */
    @EJB
    private StudentsToCoursesDAO studentsToCoursesDAO;

    /**
     * Define student DAO layer.
     */
    @SuppressWarnings({"EjbEnvironmentInspection"})
    @EJB
    private StudentDAO studentDAO;

    @SuppressWarnings({"EjbEnvironmentInspection"})
    @EJB
    private UserDAO userDAO;

    /**
     * Bean validation factory.
     */
    @SuppressWarnings({"EjbEnvironmentInspection"})
    @Resource
    private ValidatorFactory validatorFactory;

    /** Define service layer for User */
    @EJB
    private User userService;

    /** Define service layer for Students. */
    @EJB
    private Student studentService;

    /** Define mail EJB. */
    @SuppressWarnings({"EjbEnvironmentInspection"})
    @EJB
    private Mail mail;

    @EJB
    private Svn svnService;

    /**
     * Define logger.
     */
    final private static Logger logger = org.slf4j.LoggerFactory.getLogger(Course.class);

    // Constructors
    public Course() {
        logger.debug("Course - invocation.");
    }

    /**
     * Method return list of found entities instance or null if the entities does not exist
     * @return List of found entities instance or null if the entities does not exist
     * @throws ServiceException
     */
    public List<CourseEntity> getAvailableCourses() throws ServiceException {
        logger.debug("Method getAvailableCourses () - invocation.");
        List<CourseEntity> availableCourses;

        try {
            availableCourses = new ArrayList<>();
            for (CourseEntity currentEntity : dao.findAll()) {
                availableCourses.add(currentEntity);
            }
        } catch (DAOException e) {
            logger.debug("Method getAvailableCourses () - finish with DAOException.");
            throw new ServiceException("getAvailableCourses Exception" + e.getMessage());
        }
        return availableCourses;
    }

    /**
     * Get course by its ID.
     * In case of success, method return {@link CourseEntity}, either null will be returned.
     *
     * @param courseId                      ID of course, that should be find in DB.
     * @return                              CourseEntity or null, if no course can't be found.
     * @throws IllegalArgumentException     If courseId is null or less then 1.
     */
    public CourseEntity getCourseById(Long courseId) throws IllegalArgumentException {
        logger.debug("Method getCourseById () - invocation.");

        if (courseId == null || courseId < 1) {
            logger.error("getCourseById() Course ID can't be {}", courseId);
            logger.debug("getCourseById() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Course ID can't be null or less then 1.");
        }

        CourseEntity course = null;
        logger.debug("getCourseById() Trying o find course with ID {}", courseId);

        try {
            course = dao.findById(courseId);
            logger.debug("Course found. {}", course);
        } catch (DAOException e) {
            logger.error("getCourseById() Can't find course. Error message: {}, \nTrace:\n {}", e.getMessage(),
                    Arrays.deepToString(e.getStackTrace()));
            logger.debug("Method getCourseById () - finish with DAOException.");
        }
        logger.debug("getCourseById() - finished.");

        return course;
    }

    /**
     * Persist record into DB.
     * In case, when course is a new one, record will be inserted, either -- updated.
     * After creating/updating course SVN repository for the course will be created.
     *
     * If course id null, IllegalArgumentException will be thrown.
     * If CourseEntity can't be validated, ServiceValidationException will be thrown.
     * If parent DAOException appeared, ServiceException will be thrown.
     *
     * @param course                            CourseEntity object.
     * @throws IllegalArgumentException         If course is null.
     * @throws ServiceValidationException       If CourseEntity can't be validated.
     * @throws ServiceException                 If parent DAOException appeared.
     */
    public void persist(CourseEntity course) throws IllegalArgumentException, ServiceValidationException, ServiceException {
        logger.debug("persist() - started.");

        if (course == null) {
            logger.error("persist() Course can't be {}", course);
            logger.debug("persist() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Course can't be null");
        }

        logger.debug("persist() Starting validation.");
        Set<ConstraintViolation<CourseEntity>> validationResult = validatorFactory.getValidator().validate(course);
        if (!validationResult.isEmpty()) {
            logger.error("persist() Can't validate course Entity. Got error: {}", validationResult);
            logger.debug("persist() - finished with ServiceValidationException.");
            throw new ServiceValidationException(validationResult);
        }

        logger.debug("persist() Checking is this record new by checking ID field.");
        boolean isExists = isExists(course);

        try {
            if (isExists) {
                logger.debug("persist() Updating record.");
                dao.update(course);
            } else {
                logger.debug("persist() Inserting record.");
                Long id = dao.create(course);
                course.setId(id);

                String repoName = svnService.createRepository(getCourseById(course.getId()));
                course.setRepoName(repoName);

                GoogleCalendarDTO calendarDTO = new GoogleCalendarDTO();
                calendarDTO.setCalendarName(repoName);
                calendarDTO.setCalendarDescription(course.getShortDescription());
                com.google.api.services.calendar.model.Calendar gcalendar =
                        GoogleCalendar.GCalendar.create(calendarDTO);

                if (gcalendar != null) {
                    course.setCalendarId(gcalendar.getId());
                }

                dao.update(course);        // Update repository name & calendar.
            }
        } catch (DAOException e) {
            logger.error("persist() Course can't be persist, got parent DAOException with message {}, \nTrace:\n",
                    e.getMessage(), Arrays.deepToString(e.getStackTrace()));
            logger.debug("persist() - finished with ServiceException.");
            throw new ServiceException("Course can't be persists. Error message: " + e.getMessage());
        } catch (GoogleCalendarCreatingException e) {
            logger.error("Can't create Google calendar. Error: {}", e.getMessage());
        } catch (GoogleAuthException e) {
            logger.error("Can't authorize on Google services. Error: {}", e.getMessage());
        } catch (GoogleTransportException e) {
            logger.error("Can't connect to Google services. Error: {}", e.getMessage());
        }

        logger.debug("persist() - finished.");
    }

    /**
     * Remove record from DB.
     * In case of success, True will be returned.
     *
     * If course is null, IllegalArgumentException will be thrown.
     * If parent DAOException appeared, ServiceException will be thrown.
     *
     * @param course                        CourseEntity object.
     * @return                              True if record success removed.
     * @throws IllegalArgumentException     If course is null.
     * @throws ServiceException             If parent DAOException appeared.
     */
    public boolean delete(CourseEntity course) throws IllegalArgumentException, ServiceException {
        logger.debug("delete() - started.");

        if (course == null) {
            logger.error("Course entity can't be {}", course);
            logger.debug("delete() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Course entity can't be null");
        }

        if (!isExists(course)) {
            logger.debug("delete() - finished. Record not exists in DB.");
            return false;
        }

        logger.debug("delete() Removing course: {} from DB", course);
        boolean isDeleted = false;
        try {
            isDeleted = dao.delete(course);
            logger.info("delete() Record removed from DB.");
            logger.debug("delete() - finished.");
            return isDeleted;
        } catch (DAOException e) {
            logger.error("delete() Can't remove record from DB, got DAOException with error: {} with \nTrace:\n {}",
                    e.getMessage(), Arrays.deepToString(e.getStackTrace()));
            logger.debug("delete() - finished with ServiceException.");
            throw new ServiceException("Can't remove course from DB. Error message: " + e.getMessage());
        }
    }

    /**
     * Check record for existence in DB.
     * If record presents in DB, True will be returned, either false.
     * If course is null, IllegalArgumentException will be thrown.
     *
     * @param course                        CourseEntity object.
     * @return                              True if record presents in DB.
     * @throws IllegalArgumentException     If course in null.
     */
    public boolean isExists(CourseEntity course) throws IllegalArgumentException {
        logger.debug("isNew() - started.");
        if (course == null) {
            logger.error("isNew() Course entity can't be {}", course);
            logger.debug("isNew() finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Course entity can't be null");
        }
        boolean isExists = ((course.getId() != 0) && getCourseById(course.getId()) != null);

        logger.debug("isExists() Record is {} in DB", ((isExists) ? "exists" : "not exists"));
        logger.debug("isNew() - finished.");
        return isExists;
    }

    /**
     * Constructs and return valid empty entity object
     *
     * @return Empty valid CourseEntity object
     */
    public CourseEntity getEmptyEntity() {
        CourseEntity entity = new CourseEntity();
        String str = new String();
        entity.setTitle(str);
        entity.setDescription(str);
        entity.setShortDescription(str);
        entity.setSyllabusLink(str);
        return entity;
    }

    /**
     * Wrapper for list of methods to do enrollment of student with ID <code>studentId</code> to course with ID
     * <code>courseId</code>.
     * <p/>
     * Return <code>true</code> if record success persists in DB. Either -- <code>false</code>.
     * <p/>
     * If student hadn't confirmed email, {@link UserEmailConfirmationException} will be thrown.<br/>
     * If student hadn't already fill required fields in profile {@link StudentProfileInconsistencyException} will be
     * thrown.<br/>
     * If student had already enrolled to this course, {@link StudentAlreadyEnrolledException} will be thrown.<br/>
     * If student has {@link StandardUserStatuses#PENDING} but still tries to enroll to course,
     * {@link StudentInPendingException} will be thrown.<br/>
     *
     *
     * @param studentId                     Student ID.
     * @param courseId                      Course ID.
     * @return                              True if record success persists in DB.
     * @throws IllegalArgumentException                 If student ID or course ID less that 1.
     * @throws UserEmailConfirmationException           If user email still unconfirmed.
     * @throws StudentProfileInconsistencyException     If user hadn't filled required fields yet.
     * @throws StudentAlreadyEnrolledException          If student had already been enrolled to course.
     * @throws StudentInPendingException                If student has {@link StandardUserStatuses#PENDING} status.
     */
    public boolean enrollStudentToCourse(String studentId, long courseId, String requestUrl) throws IllegalArgumentException,
            UserEmailConfirmationException, StudentProfileInconsistencyException, StudentAlreadyEnrolledException,
            StudentInPendingException {
        logger.debug("enrollStudentToCourse() - started.");

        if (studentId == null || studentId.isEmpty() || courseId < 1) {
            logger.error("Student ID & Course ID can't be less than 1.");
            logger.debug("enrollStudentToCourse() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Student ID & Course ID can't be less then 1.");
        }

        if (!userService.isEmailConfirmed(studentId)) {
            logger.error("Email isn't confirmed yet. Can't enroll.");
            logger.debug("enrollStudentToCourse() - finished with UserEmailConfirmationException.");
            throw new UserEmailConfirmationException("User email isn't confirmed, please confirm it.");
        }

        if (!studentService.isProfileOk(studentId)) {
            logger.error("Not all necessary fields were filled in. Can't enroll.");
            logger.debug("enrollStudentToCourse() - finished with StudentProfileInconsistencyException.");
            throw new StudentProfileInconsistencyException("Student profile must be filled in");
        }

        if (isEnrolledToThisCourse(studentId, courseId)) {
            logger.error("Student already enrolled to this course.");
            logger.debug("enrollStudentToCourse() - finished with StudentAlreadyEnrolledException.");
            throw new StudentAlreadyEnrolledException("Student already enrolled to course");
        }

        if (isPendingEnrollment(studentId, courseId)) {
            logger.error("Student are still waiting for approving to course");
            logger.debug("enrollStudentToCourse() - finished with StudentInPendingException.");
            throw new StudentInPendingException("Student is still in pending status.");
        }

        if (!doStudentEnrollment(studentId, courseId)) {
            logger.error("Can't finish student enrollment");
            return false;
        }

        if (!doSendNotifyToInstructor(studentId, courseId, requestUrl)) {
            logger.error("Can't send notice to course instructor.");
            return false;
        }

        if (!doSendNotifyToUser(studentId, courseId)) {
            logger.error("Can't send notice to course student.");
            return false;
        }

        return true;
    }

    /**
     * Un-enroll student from course.
     * <p/>
     * Return <code>True</code> if success un-enrolled from course. Either <code>false</code>.
     *
     * @param studentId                     Student ID.
     * @param courseId                      Course ID.
     * @return                              True if success un-enrolled.
     * @throws IllegalArgumentException     If course ID or student ID less that 1.
     * @throws StudentNotEnrolledException  If student never been enrolled to this course.
     */
    public boolean unenrollStudentFromCourse(String studentId, long courseId, String reason) throws IllegalArgumentException,
            StudentNotEnrolledException {
        logger.debug("unenrollStudentFromCourse() - started.");

        if (studentId == null || studentId.isEmpty() || courseId < 1) {
            logger.error("Student ID & Course ID can't be less than 1.");
            logger.debug("unenrollStudentFromCourse() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Student ID & Course ID can't be less then 1.");
        }

        if (!isEnrolledToThisCourse(studentId, courseId)) {
            logger.error("Student hadn't been enrolled to this course.");
            logger.debug("unenrollStudentFromCourse() - finished with StudentNotEnrolledException.");
            throw new StudentNotEnrolledException("Student hadn't already been enrolled to this course.");
        }

        if (!doUnenrollStudent(studentId, courseId)) {
            logger.error("Can't unenroll student from course");
            return false;
        }

        if (!doSendNotifyInstructorUnenrollUser(studentId, courseId, reason)) {
            logger.error("Can't send notice to Instructor.");
            return false;
        }

        logger.debug("unenrollStudentFromCourse() - finished.");
        return true;
    }

    /**
     * Check is student with userId enrolled to course with courseId.
     * Return true if so and false in other case.
     *
     * @param userId                        User ID.
     * @param courseId                      Course ID.
     * @return                              True if student enrolled to course. Either false.
     * @throws IllegalArgumentException     If no null or empty studentId and null or less than 1 courseId were sent.
     */
    public boolean isEnrolledToThisCourse(String userId, Long courseId) throws IllegalArgumentException {
        logger.debug("isEnrolledToThisCourse() - started.");

        if (courseId == null || courseId < 1) {
            logger.error("Course ID can't be {} and ", courseId);
            logger.debug("isEnrolledToThisCourse() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Course ID can't be null or less than 1.");
        }
        if (userId == null || userId.isEmpty()) {
            logger.error("Student ID can't be {} and ", courseId);
            logger.debug("isEnrolledToThisCourse() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Student ID can't be null or empty.");
        }

        boolean isEnrolled = false;
        logger.debug("Checking course enrollment for user {}", userId);
        try {
            List<Integer> courses = studentsToCoursesDAO.findCoursesIdByUserId(userId);
            if (courses != null && !courses.isEmpty()) {
                logger.info("isEnrolledToThisCourse() Found {} courses. Checking enrollment.", courses.size());

                for (Integer usersToCourse : courses) {
                    logger.debug("Checking through list of courses...");
                    if (Long.valueOf(usersToCourse).equals(courseId)) {
                        logger.info("Found course.");
                        return true;
                    }
                }
                logger.info("isEnrolledToThisCourse() Course not found.");
            }
        } catch (DAOException e) {
            logger.error("Can't get course list. Error message: {}, \nTrace:\n {}", e.getMessage(),
                    Arrays.deepToString(e.getStackTrace()));
            logger.debug("isEnrolledToThisCourse() finished with false result.");
        }

        logger.debug("isEnrolledToThisCourse() - finished.");
        return isEnrolled;
    }

    /**
     * Check is student awaiting for instructor approving. E.g. has status {@link StandardUserStatuses#PENDING}.
     * <p/>
     * In case, when courseId equals to ID of one of the courses, where student had sent application of enrollment,
     * <code>true</code> will be returned. Either <code>false</code>.
     * <p/>
     * If userId is <code>null</code> or empty, IllegalArgumentException will be thrown.<br/>
     * If courseId is <code>null</code> or less than 1, IllegalArgumentException will be thrown.<br/>
     * If any DAOException appeared, <code>false</code> will be returned.
     *
     * @param userId                        User ID, to be checked.
     * @param courseId                      Course ID, to be checked.
     * @return                              True if student has pending status in course with an ID courseId. Either, false.
     * @throws IllegalArgumentException     If userId is null or empty.
     *                                      If courseId is null or less than 1.
     */
    public boolean isPendingEnrollment(String userId, Long courseId) throws IllegalArgumentException {
        logger.debug("isPendingEnrollment() - started.");

        if (userId == null || userId.isEmpty()) {
            logger.error("User ID can't be {}", userId);
            logger.debug("isPendingEnrollment() - finished with IllegalArgumentException");
            throw new IllegalArgumentException("User ID can't be null or empty");
        }

        if (courseId == null || courseId < 1) {
            logger.error("Course ID can't be {}", courseId);
            logger.debug("isPendingEnrollment() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Course ID can't be null or less than 1");
        }

        boolean isPending = false;
        logger.debug("Checking pending status of student.");
        try {
            List<Integer> courses = studentsToCoursesDAO.findCoursesIdByUserId(userId, StandardUserStatuses.PENDING);
            if (courses != null && !courses.isEmpty()) {
                for (Integer userToCourse : courses) {
                    if (Long.valueOf(userToCourse).equals(courseId)) {
                        logger.info("Course found.");
                        return true;
                    }
                }
            }
            logger.info("Course not found");
        } catch (DAOException e) {
            logger.error("Can't get course list. Error {}, \nTrace:\n {}", e.getMessage(), e.getStackTrace());
            logger.debug("isPendingEnrollment() - finished with false result");
        }

        logger.debug("isPendingEnrollment() - finished.");
        return isPending;
    }

    /**
     * Return number of students enrolled courses.
     * <p/>
     * In case of success, return number of enrolled courses (can be 0 or more).<br/>
     * If studentId is <code>null</code> or empty, IllegalArgumentException will be thrown.
     *
     * @param userId                        User ID.
     * @return                              Number of enrolled courses for student with userId.
     * @throws IllegalArgumentException     If userId is null or empty.
     */
    public Long countMyCourses(String userId) throws IllegalArgumentException {
        logger.debug("countMyCourses() - started.");

        if (userId == null || userId.isEmpty()) {
            logger.error("UserID can't be {}", userId);
            logger.debug("countMyCourses() - finished with IllegalArgumentException. ");
            throw new IllegalArgumentException("User ID can't be null or empty");
        }

        logger.debug("Count course subscription.");
        Long counter = 0l;
        try {
            counter = studentsToCoursesDAO.countCoursesByUserId(userId);

        } catch (DAOException e) {
            logger.error("Can't count user's courses, got error. Message: {}, \nTrace:\n {}", e.getMessage(),
                    e.getStackTrace());
            logger.debug("countMyCourses() - finished with 0 result.");
        }

        logger.debug("Result is {} courses subscribed.", counter);
        logger.debug("countMyCourses() - finished.");
        return counter;
    }

    /**
     * Return list of students courses by him / her userId.
     * <p/>
     * If user ID is null or empty, IllegalArgumentException will be thrown.
     *
     * @param userId                        User ID.
     * @return                              List of Course entities or empty list.
     * @throws IllegalArgumentException     If userId is null or empty.
     *
     */
    public List<CourseEntity> getStudentsCourses(String userId) throws IllegalArgumentException {
        logger.debug("getStudentsCourses() - started.");

        if (userId == null || userId.isEmpty()) {
            logger.error("User ID can't be {}", userId);
            logger.debug("getStudentsCourses() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("User ID can't be null or empty.");
        }

        logger.debug("Trying to find users courses");
        List<CourseEntity> courses = new ArrayList<>();
        try {
            List<Integer> courseIds = studentsToCoursesDAO.findCoursesIdByUserId(userId);
            if (courseIds != null) {
                logger.info("Found {} courses", courseIds.size());

                for (Integer id : courseIds) {
                    courses.add(dao.findById(Long.valueOf(id)));
                }
            }
        } catch (DAOException e) {
            logger.error("Got parent DAOException. Error message: {}", e.getMessage());
            logger.debug("Empty list will be returned.");
        }

        logger.debug("getStudentsCourses() - finished.");
        return courses;
    }

    /**
     * Return approve hash for user to course relation.
     * <p/>
     * This hash could be send to course instructor for approving students enrollment to course.
     * In case of success, MD5 hash will be returned, or <code>null</code> if hash can't be calculated.
     * <p/>
     * If userId is <code>null</code> or empty, IllegalArgumentException will be thrown.<br/>
     * If courseId is <code>null</code> or less than 1, IllegalArgumentException will be thrown.<br/>
     * If parent DAOException appeared, {@link ServiceException} will be produced.
     *
     * @param userId                            User ID.
     * @param courseId                          Course ID.
     * @return                                  String hash.
     * @throws IllegalArgumentException         If <code>userId</code> is null or empty.
     *                                          If <code>courseId</code> is null or less than 1.
     * @throws ServiceException                 If parent DAOException appeared.
     */
    public String getStudentToCourseApproveMd5(String userId, Long courseId) throws IllegalArgumentException,
            ServiceException {
        logger.debug("getStudentToCourseApproveMd5() - started.");

        if (userId == null || userId.isEmpty()) {
            logger.error("User ID can't be {}", userId);
            logger.debug("getStudentToCourseApproveMd5() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("User ID can't be null or empty.");
        }

        if (courseId == null || courseId < 1) {
            logger.error("Course ID can't be {}", courseId);
            logger.debug("getStudentToCourseApproveMd5() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Course ID can't be null or less than 1.");
        }

        logger.debug("Trying to calculate md5 for user to course approve link.");
        String md5 = null;
        try {
            md5 = studentsToCoursesDAO.getApproveMD5(userId, courseId);
            logger.info("Got MD5 {}", md5);
        } catch (DAOException e) {
            logger.error("Got DAOException with error message: {}", e.getMessage());
            throw new ServiceException("Can't calculate checksum. Got DAOException: " + e.getMessage());
        }

        logger.debug("getStudentToCourseApproveMd5() - finished.");
        return md5;
    }

    /**
     * Approve student enrollment to course.
     * <p/>
     * In case of success <code>true</code> will be returned, either -- <code>false</code>.
     *
     * @param md5                           Confirmation hash.
     * @param url                           Basic application URL, where student should be moved, by clicking
     *                                      on link "my course". I.e. http://localhost/cms/ or sth similar.
     * @return                              True if students application was successful endorsed.
     * @throws IllegalArgumentException     If md5 hash is null or empty.
     */
    public boolean approveStudentEnrollment(String md5, String url) throws IllegalArgumentException {
        logger.debug("approveStudentEnrollmentByStudentId() - started.");

        if (md5 == null || md5.isEmpty()) {
            logger.error("Hash can't be {}", md5);
            logger.debug("approveStudentEnrollmentByStudentId() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Hash can't be null or empty");
        }

        boolean isApproved = false;
        UsersToCourseId id = doChangeUserStatusToEnrolled(md5);
        if (id.getCourseId() > 0 && id.getUserId() != null && !id.getUserId().isEmpty()) {
            if (doGenerateSvnLink(id)) {
                isApproved = doSendNotifyToApprovedUser(id.getUserId(), id.getCourseId(), url);
            }
        }

        logger.debug("approveStudentEnrollmentByStudentId() - finished.");
        return isApproved;
    }

    /**
     * Discard student's application to course.
     *
     * @param md5                           Hash.
     * @param url                           Base URL, used to send notice for user.
     * @return                              True if success discarded.
     * @throws IllegalArgumentException     If md5 is null or empty.
     */
    public boolean discardStudentEnrollment(String md5, String url) throws IllegalArgumentException {
        logger.debug("discardStudentEnrollment() - started.");

        if (md5 == null || md5.isEmpty()) {
            logger.error("MD5 checksum can't be {}", md5);
            logger.debug("discardStudentEnrollment() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Hash can't be null or empty.");
        }

        boolean isDiscarded = false;
        UsersToCourseId id = doDiscardStudentEnrollment(md5);

        if (id != null) {
            isDiscarded = doSendNotifyToDiscardedUser(id, url);
        }

        logger.debug("discardStudentEnrollment() - finished.");
        return isDiscarded;
    }


    /**
     * Return course instructors list.
     * <p/>
     * In case of success, ArrayList of UserEntity with filled email & name will be returned.
     * <p/>
     * If courseId is <code>null</code> or less than 1, IllegalArgumentException will be thrown.
     *
     * @param courseId                      Course ID.
     * @return                              List of {@link UserEntity} or empty list.
     * @throws IllegalArgumentException     If course ID is null or less than 1.
     */
    private List<UserEntity> getCourseInstructorsContact(Long courseId) throws IllegalArgumentException {
        logger.debug("getCourseInstructorContact() - started.");

        if (courseId == null || courseId < 1) {
            logger.error("Course ID can't be {}", courseId);
            logger.debug("getCourseInstructorContact() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Course ID can't be null or less than 1");
        }

        List<UserEntity> instructorsList = new ArrayList<>();

        // @TODO: Fix this to real instructors values.
        UserEntity instructor = new UserEntity();
        instructor.setEmail(DummyInstructorDTO.INSTR_EMAIL);
        instructor.setName(DummyInstructorDTO.INSTR_NAME);

        instructorsList.add(instructor);

        logger.debug("getCourseInstructorContact() - finished.");
        return instructorsList;
    }

    /**
     * Return <code>true</code> if user success added to student to course relation with pending status.
     *
     * @param userId                User ID.
     * @param courseId              Course ID.
     * @return                      True if student added to course with pending status.
     */
    private boolean doStudentEnrollment(String userId, Long courseId) {
        logger.debug("doStudentEnrollment() - started.");

        boolean isCreated = false;
        try {
            CourseEntity courseEntity = dao.findById(courseId);
            UserEntity studentEntity = userDAO.findById(userId);

            UsersToCourseEntity entity = new UsersToCourseEntity();
            entity.setCourseId(courseEntity);
            entity.setUserId(studentEntity);
            logger.debug("Creating entity: {}", entity);

            isCreated = studentsToCoursesDAO.create(entity);
        } catch (DAOException e) {
            logger.error("Can't enroll student {} to course {}", userId, courseId);
            logger.debug("enrollStudentToCourse() - finished with false.");
        }
        logger.debug("doStudentEnrollment() - finished.");
        return isCreated;
    }

    /**
     * Notify instructor, that student with ID <code>userId</code> enrolled to course with ID <code>courseId</code>
     * with status pending and awaiting for approving by clicking to link <code>url</code>.
     * <p/>
     * If mail was successful sent, <code>true</code> will be returned. Either (including parent exceptions)
     * -- <code>false</code>.
     *
     * @param userId                User ID
     * @param courseId              Course ID.
     * @param url                   Activation URL.
     * @return                      True if mail sent successful.
     */
    private boolean doSendNotifyToInstructor(String userId, Long courseId, String url) {
        logger.debug("doSendNotifyToInstructor - started.");

        boolean isSent = false;

        try {
            List<UserEntity> instructorsList = getCourseInstructorsContact(courseId);

            StudentEntity studentEntity = new StudentEntity();
            UserEntity userEntity = userDAO.findById(userId);
            studentEntity.setUserId(userEntity);

            StudentEnrolledToCourse notify = new StudentEnrolledToCourse();
            notify.setStudentEntity(studentDAO.findByUserId(studentEntity));
            notify.setCourse(getCourseById(courseId));
            notify.setInstructorList(instructorsList);
            notify.setStudent(userDAO.findById(userId));
            notify.setApproveStudentLink(url + "/course/enrollApprove");
            notify.setDiscardLink(url + "/course/enrollDiscard");
            mail.send(notify);
            isSent = true;
        } catch (DAOException e) {
            logger.error("Can't find user entity. Message: {}", e.getMessage());
        } catch (Exception e) {
            logger.error("Got exception. Message: {}", e.getMessage());
        }

        logger.debug("doSendNotifyToInstructor - finished.");
        return isSent;
    }

    /**
     * Send notice to user with ID <code>studentId</code> about his / her enrollment to course
     * with ID <code>courseId</code>.
     * <p/>
     * In case of success, <code>true</code> will be returned, either <code>false</code>.
     *
     * @param studentId         User ID.
     * @param courseId          Course ID.
     * @return                  True if message was sent.
     */
    private boolean doSendNotifyToUser(String studentId, Long courseId) {
        logger.debug("doSendNotifyToUser - started.");

        boolean isSent = false;
        try {
            UserEntity user = userDAO.findById(studentId);
            CourseEntity courseEntity = getCourseById(courseId);
            if (user != null && courseEntity != null) {
                NotifyStudentEnrollToCourse notify = new NotifyStudentEnrollToCourse();
                notify.setUser(user);
                notify.setCourse(courseEntity);
                mail.send(notify);
                isSent = true;
            }
        } catch (DAOException | ServiceValidationException e) {
            logger.error("Can't find user.");
        }

        logger.debug("doSendNotifyToUser - finished.");
        return isSent;
    }

    /**
     * Change status to {@link StandardUserStatuses#ENROLLED} in UsersToCourses entity by hash.
     * <p/>
     * In case of success, filled {@link UsersToCourseId} object will be returned.
     * <p/>
     * If md5 is <code>null</code> or empty, IllegalArgumentException will be thrown.
     *
     * @param md5                           Confirmation Hash.
     * @return                              Instance of UsersToCourseId with user & course ids.
     * @throws IllegalArgumentException     If hash is null or empty.
     */
    private UsersToCourseId doChangeUserStatusToEnrolled(String md5) throws IllegalArgumentException {
        logger.debug("doChangeUserStatus() - started.");
        if (md5 == null || md5.isEmpty()) {
            logger.error("Hash can't be {}", md5);
            logger.debug("approveStudentEnrollmentByStudentId() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Hash can't be null or empty");
        }

        UsersToCourseEntity entity = null;
        UsersToCourseId id = new UsersToCourseId();
        try {
            entity = studentsToCoursesDAO.changeStudentStatus(md5, StandardUserStatuses.ENROLLED);
            if (entity != null) {
                id.setCourseId(entity.getCourseId().getId());
                id.setUserId(entity.getUserId().getId());
            }
        } catch (DAOException e) {
            logger.error("Can't change status. Got DAOException: {}", e.getMessage());
            logger.debug("False will be returned.");
        }

        logger.debug("doChangeUserStatus() - finished.");
        return id;
    }

    /**
     * Notify user with ID <code>userId</code>, that he / she was successful endorsed
     * to be a student on course with ID <code>courseId</code>.
     * <p/>
     * If user ID is <code>null</code> or empty, IllegalArgumentException will be thrown.
     * If course ID is <code>null</code> or less than 1, IllegalArgumentException will be thrown.
     *
     * @param userId                        User ID
     * @param courseId                      Course ID.
     * @param url                           Basic URL, where student should be send.
     * @return                              True if message was sent successfully.
     * @throws IllegalArgumentException     If user ID is null or empty
     *                                      If course ID is null or less than 1.
     */
    private boolean doSendNotifyToApprovedUser(String userId, Long courseId, String url) throws IllegalArgumentException {
        logger.debug("doSendNotifyToApprovedUser() - started.");

        if (userId == null || userId.isEmpty()) {
            logger.error("User ID can't be {}", userId);
            throw new IllegalArgumentException("User ID can't be null or empty");
        }

        if (courseId == null || courseId < 1) {
            logger.error("Course ID can't be {}", courseId);
            throw new IllegalArgumentException("Course ID can't be null or less than 1");
        }

        boolean isSent = false;
        try {
            UserEntity user = userDAO.findById(userId);
            CourseEntity courseEntity = getCourseById(courseId);
            if (user != null && courseEntity != null) {
                NotifyStudentSuccessEnrollment notify = new NotifyStudentSuccessEnrollment();
                notify.setBaseLink(url);
                notify.setUser(user);
                notify.setCourse(courseEntity);
                mail.send(notify);
                isSent = true;
            }
        } catch (DAOException | ServiceValidationException e) {
            logger.error("Can't find user or course.");
        }

        logger.debug("doSendNotifyToApprovedUser() - finished.");
        return isSent;
    }

    /**
     * Send notice to Instructors about student leaving.
     * <p/>
     * If notice successful sent, <code>true</code> will be returned, either <code>false</code>.
     * <p/>
     * If userID is null or empty, IllegalArgumentException will be produced.
     * If courseId is null or less than 1, IllegalArgumentException will be produced.
     *
     * @param userId                        User ID.
     * @param courseId                      Course ID.
     * @param reason                        Leaving reason.
     * @return                              True if notice was sent successfully.
     * @throws IllegalArgumentException     If userId is null or empty.
     *                                      If courseId is null or less than 1.
     */
    private boolean doSendNotifyInstructorUnenrollUser(String userId, Long courseId, String reason) throws IllegalArgumentException {
        logger.debug("doSendNotifyInstructorUnenrollUser() - started.");

        if (userId == null || userId.isEmpty()) {
            logger.error("User ID can't be {}", userId);
            logger.debug("doSendNotifyInstructorUnenrollUser() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("User ID can't be null or empty.");
        }

        if (courseId == null || courseId < 1) {
            logger.error("Course ID can't be {}", courseId);
            logger.debug("doSendNotifyInstructorUnenrollUser() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Course ID can't be null or empty");
        }

        boolean isSent = false;

        try {
            StudentUnenrolledFromCourse notify = new StudentUnenrolledFromCourse();
            notify.setCourse(getCourseById(courseId));
            notify.setInstructorList(getCourseInstructorsContact(courseId));
            notify.setStudent(userDAO.findById(userId));
            notify.setUnenrollReason(reason);
            mail.send(notify);
            isSent = true;
        } catch (Exception e) {
            logger.error("Can't send notify, Exception: {}, \nTrace:\n {}", e.getMessage(), e.getStackTrace());
        }

        logger.debug("doSendNotifyInstructorUnenrollUser() - finished.");
        return isSent;
    }

    /**
     * Do student unenrollment from course.
     * <p/>
     * In case of success, <code>true</code> will be returned, either <code>false</code>.
     * <p/>
     * If user ID is null or empty, IllegalArgumentException will be thrown.
     * If course ID is less than 1, IllegalArgumentException will be thrown.
     *
     * @param userId                        User ID.
     * @param courseId                      Course ID.
     * @return                              True if user removed from course.
     * @throws IllegalArgumentException     If userId is null or empty.
     *                                      If courseId is less than 1.
     */
    private boolean doUnenrollStudent(String userId, long courseId) throws IllegalArgumentException {
        logger.debug("doUnenrollStudent() - started.");

        if (userId == null || userId.isEmpty()) {
            logger.error("User ID can't be {}", userId);
            logger.debug("doUnenrollStudent() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("User ID can't be null or empty");
        }

        if (courseId < 1) {
            logger.error("Course ID can't be {}", userId);
            logger.debug("doUnenrollStudent() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("Course ID can't be less than 1.");
        }

        boolean isUnenrolled = false;

        try {
            UsersToCourseId id = new UsersToCourseId();
            id.setCourseId(courseId);
            id.setUserId(userId);
            logger.debug("Deleting entity: {}", id);

            studentsToCoursesDAO.delete(id);
            logger.info("record removed success.");
            return true;
        } catch (DAOException e) {
            logger.error("Can't enroll student {} to course {}", userId, courseId);
            logger.debug("unenrollStudentFromCourse() - finished with false.");
        }

        logger.debug("doUnenrollStudent() - finished.");
        return isUnenrolled;
    }

    /**
     * Discard user to being student at this course.
     * <p/>
     * In case of success, UserToCourse ID of discarded student will be returned. In case of fails, null will be
     * returned.
     *
     * @param md5           MD5.
     * @return              Removed student ID or null.
     */
    private UsersToCourseId doDiscardStudentEnrollment(String md5) {
        logger.debug("doDiscardStudentEnrollment() - started.");

        UsersToCourseEntity u2c = null;

        try {
            u2c = studentsToCoursesDAO.findByMd5(md5);
        } catch (DAOException e) {
            logger.error("Can't find users to course relation. Got DAOException. Message: {} and \ntrace:\n {}",
                    e.getMessage(), e.getStackTrace());
            logger.debug("Return null.");
        }

        if (u2c != null) {
            UsersToCourseId id = new UsersToCourseId();
            id.setUserId(u2c.getUserId().getId());
            id.setCourseId(u2c.getCourseId().getId());

            try {
                logger.debug("doDiscardStudentEnrollment() - finished.");

                return (studentsToCoursesDAO.delete(id))? id : null;
            } catch (DAOException e) {
                logger.error("Can't discard student from course. Message: {} and \ntrace\n: {}", e.getMessage(),
                        e.getStackTrace());
                logger.debug("Return null.");
            }
        }
        return null;
    }

    /**
     * Send email notice to user about his / her discards on course.
     * In case, when message successful sent, return true, either - false.
     *
     * @param id            Discarded UserToCourseId.
     * @param url           Base URL.
     * @return              True if message successful sent.
     */
    private boolean doSendNotifyToDiscardedUser(UsersToCourseId id, String url) {
        logger.debug("doSendNotifyToDiscardedUser() - started.");

        boolean isSent = false;
        try {
            UserEntity user = userDAO.findById(id.getUserId());
            CourseEntity courseEntity = getCourseById(id.getCourseId());
            if (user != null && courseEntity != null) {
                NotifyStudentDiscardedEnrollment notify = new NotifyStudentDiscardedEnrollment();
                notify.setBaseUrl(url);
                notify.setUser(user);
                notify.setCourse(courseEntity);
                mail.send(notify);
                isSent = true;
            }
        } catch (DAOException | ServiceValidationException e) {
            logger.error("Can't find user or course.");
        }

        logger.debug("doSendNotifyToDiscardedUser() - finished.");
        return isSent;
    }

    private boolean doGenerateSvnLink(UsersToCourseId id) {
        logger.debug("doGenerateSvnLink() - started.");

        boolean isUpdated = false;

        try {
            UsersToCourseEntity u2c = studentsToCoursesDAO.findByUsersToCourseId(id);
            CourseEntity courseEntity = dao.findById(id.getCourseId());
            UserEntity userEntity = userDAO.findById(id.getUserId());

            if (u2c != null) {
                String svnLink = svnService.getUserUrl(courseEntity, userEntity);
                u2c.setSvnLink(svnLink);
                studentsToCoursesDAO.changeStudentSvnLink(u2c);
                svnService.updateUserRights();
                isUpdated = true;
            }
        } catch (DAOException e) {
            logger.error("Can't get User to Course Relation. Got DAOException with message: {}", e.getMessage());
        } catch (ServiceException e) {
            logger.error("Can't get SVN URL. Got Service Exception: {}", e.getMessage());
        }

        logger.debug("doGenerateSvnLink() - finished.");
        return isUpdated;
    }


    /**
     * Return list of students enrolled to course by course Id.
     * <p/>
     * If course ID is null, IllegalArgumentException will be thrown.
     *
     * @param courseId                      Course ID.
     * @return                              List of UsersToCourseEntity entities or empty list.
     * @throws IllegalArgumentException     If courseId is null or empty.
     *
     */
    public List<UsersToCourseEntity> getCourseStudents(Long courseId) throws IllegalArgumentException {
        logger.debug("getCourseStudents() - started.");

        if (courseId == null) {
            logger.error("User ID can't be {}", courseId);
            logger.debug("getCourseStudents() - finished with IllegalArgumentException.");
            throw new IllegalArgumentException("User ID can't be null or empty.");
        }

        logger.debug("Trying to find course students");
        try {
            return studentsToCoursesDAO.findStudentsByCourse(courseId);
        } catch (DAOException e) {
            logger.error("Got parent DAOException. Error message: {}", e.getMessage());
            logger.debug("Empty list will be returned.");
        }
        logger.debug("getCourseStudents() - finished.");
        return new LinkedList();
    }

}
